# -*- coding: utf-8 -*-
# @Time    : 2024/6/14 15:30
# @Author  : yujiahao
# @File    : 24_hashlib_collections.py
# @description:Python中的collections模块和hashlib模块


# todo 一、hashilib

'''

【hashlib】 模块是 Python 标准库中的一个模块，用于实现各种安全哈希和消息摘要算法。 【简单来说就是做加密和校验使用】
            工作原理就是：通过一个函数，把任意长度的数据按照一定规则转换为一个固定长度的数据串（通常用16进制的字符串表示）。

【常见哈希算法】
    MD5：生成128位（16字节）的哈希值。尽管速度快，但由于已被发现的安全漏洞，MD5不再推荐用于安全应用。
    SHA-1：生成160位（20字节）的哈希值。SHA-1也存在已知的安全漏洞，逐渐被淘汰。
    SHA-2：包括 SHA-224、SHA-256、SHA-384 和 SHA-512，分别生成224、256、384 和 512 位的哈希值。SHA-2 系列算法被广泛推荐用于安全应用。
    SHA-3：是最新的 SHA 系列算法，提供与 SHA-2 类似的安全性，但采用不同的内部结构。

【hashlib的特征以及使用要点】

    1、bytes类型数据 ---> 通过hashlib算法 ---> 固定长度的字符串
    2、不同的bytes类型数据转化成的结果一定不同。
    3、相同的bytes类型数据转化成的结果一定相同。
    4、此转化过程不可逆。

【hashlib的主要用途有两个】

    - 密码的加密。
    - 文件一致性校验。

'''
import hashlib

# todo 1.1、 普通加密


# 必须是bytes类型才能够进行加密
md5 = hashlib.md5()

# 第一次更新和计算哈希值
md5.update('123456'.encode('utf-8'))
print('普通加密之后的哈希值：', md5.hexdigest())  # 输出第一次哈希值

# 第二次更新和计算哈希值
md5.update(b'123456')  # 等价：md5.update('123456'.encode('utf-8'))

'''这两个不一样的原因是:
    b'123456' 添加到哈希对象的已有数据中。此时，哈希对象的状态包含了两次 b'123456'，即 b'123456123456'。
    所以每次计算哈希值的时候要使用独立的哈希对象计算，这个要注意。而且相同的bytes的hash一定是相同的
'''
print('普通加密之后的哈希值：', md5.hexdigest())  # 输出第二次哈希值，

# todo 1.2、 加盐

'''上面就是普通的md5加密，非常简单，几行代码就可以了，但是这种加密级别是最低的，相对来说不很安全。
虽然说hashlib加密是不可逆的加密方式，但也是可以破解的，那么他是如何做的呢？你看网上好多MD5解密软件，他们使用撞库的方式。
他们会把常用的一些密码比如：123456,111111,以及他们的md5的值做成对应关系，类似于字典，

    dic = {'e10adc3949ba59abbe56e057f20f883e': 123456}

循环他们那定义的字典中的键和咱们生成的密文进行比较,比较成功后通过你的密文获取对应的密码。
所以针对刚才说的情况，我们有更安全的加密方式：【加盐】

【固定的盐】
        什么叫加盐？加盐这个词儿来自于国外，外国人起名字我认为很随意，这个名字来源于烧烤，俗称BBQ。
    我们烧烤的时候，一般在快熟的时候，都会给肉串上面撒盐，增加味道，那么这个撒盐的工序，外国人认为比较复杂，
    所以就将比较复杂的加密方式称之为加盐。

'''
# todo 1.2.1、固定的盐
ret = hashlib.md5('西北'.encode('utf-8'))  # 西北就是固定的盐
ret.update('张学友'.encode('utf-8'))
print('固定加盐加密之后的哈希值：', ret.hexdigest())

'''
上面的【西北】就是固定的盐，比如你在一家公司，公司会将你们所有的密码在md5之前增加一个固定的盐，这样提高了密码的安全性。
但是如果黑客通过手段窃取到你这个固定的盐之后，也是可以破解出来的。所以，我们还可以加动态的盐。
'''

# todo 1.2.2、动态的盐


username = '游客100'
ret = hashlib.md5(username[::2].encode('utf-8'))  # 针对于每个账户，每个账户的盐都不一样
ret.update('a'.encode('utf-8'))
print('动态加盐加密之后的哈希值：', ret.hexdigest())

# todo 1.3、其他加密

'''hahslib模块是一个算法集合，他里面包含很多种加密算法，刚才我们说的MD5算法是比较常用的一种加密算法，一般的企业用MD5就够用了。
但是对安全要求比较高的企业，比如金融行业，MD5加密的方式就不够了，得需要加密方式更高的，比如sha系列，sha1,sha224,sha512等等，
数字越大，加密的方法越复杂，安全性越高，但是效率就会越慢。'''

ret = hashlib.sha1()
ret.update('yujiahao'.encode('utf-8'))
print(ret.hexdigest())

# 也可加盐
ret = hashlib.sha384(b'asdfghjk')
ret.update('yujiahao'.encode('utf-8'))
print(ret.hexdigest())

# 也可以加动态的盐
ret = hashlib.sha384(b'ghiuhy'[::2])
ret.update('yujiahao'.encode('utf-8'))
print(ret.hexdigest())

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 1.4、文件的一致性校验


"""
hashlib模块除了可以用于密码加密之外，还有一个常用的功能，那就是文件的一致性校验。

Linux讲究：一切皆文件，我们普通的文件，是文件，视频，音频，图片，以及应用程序等都是文件。
我们都从网上下载过资源，比如从网上下载Python解释器，当时可能没有注意过，
其实下载的时候都是带一个MD5或者SHA值的，因为我们的网络世界是很不安全的，经常会遇到病毒，
木马等，有些我们是看不到的可能就植入了咱们的电脑中，他们都是通过网络传入来的，
就是在网上下载一些资源的时候，趁虚而入，当然大部分被我们的浏览器或者杀毒软件拦截了，
但是还有一部分偷偷的进入磁盘中了。那么我们自己如何验证我们下载的资源是否有病毒呢？
这就需要文件的一致性校验了。

在我们下载一个软件时，往往都带有一个MD5或者SHA值，当我们下载完成这个应用程序时你要是对比大小
根本看不出什么问题，你应该对比他们的MD5值，如果两个MD5值相同，就证明这个应用程序是安全的，
如果你下载的这个文件的MD5值与服务端给你提供的不同，那么就证明你这个应用程序肯定是植入病毒了
（文件损坏的几率很低），那么你就应该赶紧删除，不应该安装此应用程序。

我们之前说过，MD5计算的就是bytes类型的数据的转换值，同一个bytes数据用同样的加密方式转化成的
结果一定相同，如果不同的bytes数据（即使一个数据只是删除了一个空格）那么用同样的加密方式转化
成的结果一定是不同的。所以，hashlib也是验证文件一致性的重要工具。
"""

input_path = '../../../input/美女模特空姐护士联系方式.txt'


# 简单文件校验：

def func(file):
    with open(file, mode='rb') as f1:
        ret = hashlib.md5()
        ret.update(f1.read())
        return ret.hexdigest()


print(func(input_path))

# 分段合并校验
'''如果文件太大，会把内存撑爆，这里hashlib可以支持分段更新，结果是一样的.

    工作原理
        - 打开文件：以二进制模式打开指定文件。
        - 创建哈希对象：创建一个 MD5 哈希对象，用于计算文件的哈希值。
        - 读取文件内容：通过循环每次读取 1024 字节的数据块，直到文件读取完毕。
        - 更新哈希对象：将读取到的数据块更新到哈希对象中，累积计算哈希值。
        - 返回哈希值：文件读取完毕后，返回计算得到的 MD5 哈希值的十六进制表示。

'''


def fun():
    with open(input_path, mode='rb') as f1:
        ret = hashlib.md5()
        while True:
            data = f1.read(1024)
            if data:
                ret.update(data)
            else:
                return ret.hexdigest()


print(fun())

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo  二、 collections模块

'''
collections 模块是 Python 标准库的一部分，提供了几种有用的集合数据类型，这些数据类型比内置的集合（如列表、字典、元组等）更为灵活和强大。
 
    1. namedtuple()
        namedtuple() 是一个工厂函数，用于创建具名元组。具名元组是一种子类化的元组，允许通过属性名来访问元素。
    
    2. deque
        deque 是双端队列，支持在两端快速添加和删除元素。它是线程安全的，适合用于需要快速插入和删除操作的场景。
    
    3. Counter
        Counter 是一个计数器，用于统计可哈希对象的频率。它是字典的子类，键是元素，值是元素的计数。
    
    4. OrderedDict
        OrderedDict 是有序字典，记住了元素插入的顺序。与普通字典不同，当你遍历 OrderedDict 时，元素会按插入顺序返回。
    
    5. defaultdict
        defaultdict 是字典的子类，提供了一个默认值，当访问的键不存在时，会返回这个默认值，而不是引发 KeyError。
    
    6. ChainMap
        ChainMap 用于将多个字典或其他映射合并为一个视图。它在字典查找时会按顺序搜索多个字典。
    
    7. UserDict, UserList, UserString
        这些类是对内置字典、列表和字符串的包装，允许用户创建它们的子类，并且可以重写一些方法来定制行为。
'''

#  todo  2.1、 namedtuple()

''' 
tuple可以表示不变数据，例如，一个点的二维坐标就可以表示成：p = (1, 2),但是，看到(1, 2)，很难看出这个tuple是用来表示一个坐标的。
那么用nametuple()就可以：namedtuple('名称', [属性list])
'''
from collections import namedtuple

point = namedtuple('坐标', ['x', 'y'])
print(point(1, 2))

# 类似的，如果要用坐标和半径表示一个圆，也可以用namedtuple定义：
Circle = namedtuple('圆', ['x', 'y', 'r'])
print(Circle(1, 2, 3))

#  todo  2.2、 deque()

'''
使用list存储数据时，按索引访问元素很快，但是插入和删除元素就很慢了，因为list是线性存储，数据量大的时候，插入和删除效率很低。
deque是为了高效实现插入和删除操作的双向列表，适合用于队列和栈，而且线程是安全的：
'''
from collections import deque

# 创建双端队列
d = deque([1, 2, 3])

# 在两端添加元素
d.append(4)
d.appendleft(0)

print(d)  # 输出: deque([0, 1, 2, 3, 4])

# 在两端删除元素
d.pop()
d.popleft()

print(d)  # 输出: deque([1, 2, 3])

# todo 2. Counter

'''
Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型，以字典的键值对形式存储，其中元素作为key，其计数作为value。
计数值可以是任意的Interger（包括0和负数）。Counter类和其他语言的bags或multisets很相似。
'''

from collections import Counter

# 创建计数器
c = Counter('abracadabra')

print(c)  # 输出: Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

# 更新计数器
c.update('aaaaazzz')

print(c)  # 输出: Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

# todo 2.4、OrderedDict
'''
OrderedDict 是有序字典，记住了元素插入的顺序。与普通字典不同，当你遍历 OrderedDict 时，元素会按插入顺序返回【不是Key本身排序】。
'''
from collections import OrderedDict

# 创建有序字典
od = OrderedDict()

od['one'] = 1
od['two'] = 2
od['three'] = 3

print(od)  # 输出: OrderedDict([('one', 1), ('two', 2), ('three', 3)])

# 遍历有序字典
for key, value in od.items():
    print(key, value)

# todo 2.5、defaultdict

'''
有如下值集合 [11,22,33,44,55,66,77,88,99,90...]，将所有大于 66 的值保存至字典的第一个key中，将小于 66 的值保存至第二个key的值中。

即： {'k1': 大于66 , 'k2': 小于66}
'''
# 用普通的操作
li = [11, 22, 33, 44, 55, 77, 88, 99, 90]
result = {}
for row in li:
    if row > 66:
        if 'key1' not in result:
            result['key1'] = []
        result['key1'].append(row)
    else:
        if 'key2' not in result:
            result['key2'] = []
        result['key2'].append(row)
print(result)

# 用defaultdict
from collections import defaultdict

values = [11, 22, 33, 44, 55, 66, 77, 88, 99, 90]
my_dict = defaultdict(list)

for value in values:
    if value > 66:
        my_dict['k1'].append(value)
    else:
        my_dict['k2'].append(value)

print(my_dict)

# 使用dict时，如果引用的Key不存在，就会抛出KeyError。如果希望key不存在时，返回一个默认值，就可以用defaultdict：
dd = defaultdict(lambda: 'N/A')
dd['key1'] = 'abc'
# key1存在
print(dd['key1'])
dd['key2']  # key2不存在，返回默认值
print(dd['key2'])

# todo 2.6、 ChainMap

'''ChainMap 用于将多个字典或其他映射合并为一个视图。它在字典查找时会按顺序搜索多个字典。'''
from collections import ChainMap

dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}

# 创建 ChainMap
cm = ChainMap(dict1, dict2)

print(cm['a'])  # 输出: 1 (来自 dict1)
print(cm['b'])  # 输出: 2 (来自 dict1，因为 dict1 在 dict2 之前)
print(cm['c'])  # 输出: 4 (来自 dict2)

# todo 2.7、 UserDict, UserList, UserString

'''这些类是对内置字典、列表和字符串的包装，允许用户创建它们的子类，并且可以重写一些方法来定制行为。'''

from collections import UserDict


class MyDict(UserDict):
    def __setitem__(self, key, value):
        print(f'Setting {key} to {value}')
        super().__setitem__(key, value)


md = MyDict()
md['a'] = 1  # 输出: Setting a to 1
